﻿using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Xml;

namespace WholeSlideImageLibrary.Formats.PhilipsTiff
{
    internal static class PhilipsTiffXmlMetadataParser
    {
        public static PhilipsTiffXmlMetadata? Parse(byte[] xmlContent, int offset, int count)
        {
            if (xmlContent is null)
            {
                throw new ArgumentNullException(nameof(xmlContent));
            }

            using var reader = XmlReader.Create(new MemoryStream(xmlContent, offset, count, false), new XmlReaderSettings { IgnoreComments = true, IgnoreWhitespace = true });
            while (reader.Read())
            {
                switch (reader.NodeType)
                {
                    case XmlNodeType.Element:
                        if (reader.Name == "DataObject")
                        {
                            PhilipsTiffXmlMetadata? matadata = ParseDataObject(reader);
                            return matadata;
                        }
                        break;
                    case XmlNodeType.EndElement:
                        ThrowInvalidDataException();
                        break;
                }
            }

            return null;
        }

        private static PhilipsTiffXmlMetadata? ParseDataObject(XmlReader reader)
        {
            if (reader.GetAttribute("ObjectType") != "DPUfsImport")
            {
                return null;
            }

            PhilipsTiffXmlMetadata? metadata = null;
            int depth = 1;
            while (reader.Read())
            {
                switch (reader.NodeType)
                {
                    case XmlNodeType.Element:
                        depth++;
                        if (depth == 2 && reader.Name == "Attribute")
                        {
                            string name = reader.GetAttribute("Name");
                            if (name == "DICOM_MANUFACTURER")
                            {
                                metadata ??= new PhilipsTiffXmlMetadata();
                                metadata.Manufacturer = ReadContentString(reader);
                                depth--;
                                continue;
                            }
                            if (name == "PIM_DP_SCANNED_IMAGES")
                            {
                                PhilipsTiffXmlMetadataScanedImage[]? images = ParseScannedImageArray(reader);
                                if (!(images is null))
                                {
                                    metadata ??= new PhilipsTiffXmlMetadata();
                                    metadata.ScanedImages = images;
                                }
                                continue;
                            }
                        }
                        break;
                    case XmlNodeType.EndElement:
                        depth--;
                        if (depth == 0)
                        {
                            if (reader.Name == "DataObject")
                            {
                                return metadata;
                            }
                            ThrowInvalidDataException();
                        }
                        if (depth < 0)
                        {
                            ThrowInvalidDataException();
                        }
                        break;
                }
            }

            ThrowEndOfStreamException();
            return null;
        }

        private static PhilipsTiffXmlMetadataScanedImage[]? ParseScannedImageArray(XmlReader reader)
        {
            List<PhilipsTiffXmlMetadataScanedImage>? list = null;
            int depth = 0;
            bool isInArray = false;
            while (reader.Read())
            {
                switch (reader.NodeType)
                {
                    case XmlNodeType.Element:
                        if (depth == 0 && reader.Name == "Array")
                        {
                            isInArray = true;
                            depth++;
                            continue;
                        }
                        if (depth == 1 && isInArray)
                        {
                            PhilipsTiffXmlMetadataScanedImage? image = ParseScannedImage(reader);
                            if (!(image is null))
                            {
                                list ??= new List<PhilipsTiffXmlMetadataScanedImage>();
                                list.Add(image);
                            }
                            continue;
                        }
                        depth++;
                        break;
                    case XmlNodeType.EndElement:
                        depth--;
                        if (depth == 0)
                        {
                            if (!isInArray)
                            {
                                return null;
                            }
                            if (reader.Name != "Array")
                            {
                                ThrowInvalidDataException();
                            }
                            return list?.ToArray();
                        }
                        if (depth < 0)
                        {
                            ThrowInvalidDataException();
                        }
                        break;
                }
            }

            ThrowEndOfStreamException();
            return null;
        }

        private static PhilipsTiffXmlMetadataScanedImage? ParseScannedImage(XmlReader reader)
        {
            PhilipsTiffXmlMetadataScanedImage? image = null;

            int depth = 1;
            bool isDataObject = reader.Name == "DataObject" && reader.GetAttribute("ObjectType") == "DPScannedImage";
            while (reader.Read())
            {
                switch (reader.NodeType)
                {
                    case XmlNodeType.Element:
                        depth++;
                        if (depth == 2 && isDataObject && reader.Name == "Attribute")
                        {
                            string name = reader.GetAttribute("Name");
                            if (name == "DICOM_DERIVATION_DESCRIPTION")
                            {
                                image ??= new PhilipsTiffXmlMetadataScanedImage();
                                image.DerivationDescription = ReadContentString(reader);
                                depth--;
                                continue;
                            }
                            if (name == "DICOM_PIXEL_SPACING")
                            {
                                if (PhilipsTiffPixelSpacing.TryParse(ReadContentString(reader).AsSpan(), out PhilipsTiffPixelSpacing pixelSpacing))
                                {
                                    image ??= new PhilipsTiffXmlMetadataScanedImage();
                                    image.PixelSpacing = pixelSpacing;
                                }
                                depth--;
                                continue;
                            }
                            if (name == "PIIM_PIXEL_DATA_REPRESENTATION_SEQUENCE")
                            {
                                PhilipsTiffXmlMetadataImageLevel[]? sequence = ParseRepresentationSequenceArray(reader);
                                if (sequence is null)
                                {
                                    ThrowInvalidDataException();
                                }
                                image ??= new PhilipsTiffXmlMetadataScanedImage();
                                image.RepresentationSequence = sequence;
                                continue;
                            }
                        }
                        break;
                    case XmlNodeType.EndElement:
                        if (depth == 1)
                        {
                            if (!isDataObject)
                            {
                                return null;
                            }
                            if (reader.Name != "DataObject")
                            {
                                ThrowInvalidDataException();
                            }
                            return image;
                        }
                        depth--;
                        if (depth < 0)
                        {
                            ThrowInvalidDataException();
                        }
                        break;
                }
            }

            ThrowEndOfStreamException();
            return null;
        }

        private static PhilipsTiffXmlMetadataImageLevel[]? ParseRepresentationSequenceArray(XmlReader reader)
        {
            List<PhilipsTiffXmlMetadataImageLevel>? list = null;
            int depth = 0;
            bool isInArray = false;
            while (reader.Read())
            {
                switch (reader.NodeType)
                {
                    case XmlNodeType.Element:
                        if (depth == 0 && reader.Name == "Array")
                        {
                            isInArray = true;
                            depth++;
                            continue;
                        }
                        if (depth == 1 && isInArray)
                        {
                            PhilipsTiffXmlMetadataImageLevel? level = ParseRepresentationSequence(reader);
                            if (!(level is null))
                            {
                                list ??= new List<PhilipsTiffXmlMetadataImageLevel>();
                                list.Add(level);
                            }
                            continue;
                        }
                        depth++;
                        break;
                    case XmlNodeType.EndElement:
                        depth--;
                        if (depth == 0)
                        {
                            if (!isInArray)
                            {
                                return null;
                            }
                            if (reader.Name != "Array")
                            {
                                ThrowInvalidDataException();
                            }
                            return list?.ToArray();
                        }
                        if (depth < 0)
                        {
                            ThrowInvalidDataException();
                        }
                        break;
                }
            }

            ThrowEndOfStreamException();
            return null;
        }

        private static PhilipsTiffXmlMetadataImageLevel? ParseRepresentationSequence(XmlReader reader)
        {
            PhilipsTiffXmlMetadataImageLevel? level = null;
            int depth = 1;
            bool isDataObject = reader.Name == "DataObject" && reader.GetAttribute("ObjectType") == "PixelDataRepresentation";
            while (reader.Read())
            {
                switch (reader.NodeType)
                {
                    case XmlNodeType.Element:
                        depth++;
                        if (depth == 2 && isDataObject && reader.Name == "Attribute")
                        {
                            string name = reader.GetAttribute("Name");
                            if (name == "DICOM_PIXEL_SPACING")
                            {
                                if (PhilipsTiffPixelSpacing.TryParse(ReadContentString(reader).AsSpan(), out PhilipsTiffPixelSpacing pixelSpacing))
                                {
                                    level ??= new PhilipsTiffXmlMetadataImageLevel();
                                    level.PixelSpacing = pixelSpacing;
                                }
                                depth--;
                                continue;
                            }
                            if (name == "PIIM_PIXEL_DATA_REPRESENTATION_COLUMNS")
                            {
                                if (int.TryParse(ReadContentString(reader), out int number))
                                {
                                    level ??= new PhilipsTiffXmlMetadataImageLevel();
                                    level.Columns = number;
                                }
                                depth--;
                                continue;
                            }
                            if (name == "PIIM_PIXEL_DATA_REPRESENTATION_NUMBER")
                            {
                                if (short.TryParse(ReadContentString(reader), out short number))
                                {
                                    level ??= new PhilipsTiffXmlMetadataImageLevel();
                                    level.Number = number;
                                }
                                depth--;
                                continue;
                            }
                            if (name == "PIIM_PIXEL_DATA_REPRESENTATION_ROWS")
                            {
                                if (int.TryParse(ReadContentString(reader), out int number))
                                {
                                    level ??= new PhilipsTiffXmlMetadataImageLevel();
                                    level.Rows = number;
                                }
                                depth--;
                                continue;
                            }
                        }
                        break;
                    case XmlNodeType.EndElement:
                        if (depth == 1)
                        {
                            if (!isDataObject)
                            {
                                return null;
                            }
                            if (reader.Name != "DataObject")
                            {
                                ThrowInvalidDataException();
                            }
                            return level;
                        }
                        depth--;
                        if (depth < 0)
                        {
                            ThrowInvalidDataException();
                        }
                        break;
                }
            }

            ThrowEndOfStreamException();
            return null;
        }

        private static string? ReadContentString(XmlReader reader)
        {
            if (!reader.Read())
            {
                ThrowEndOfStreamException();
                return null;
            }
            if (reader.NodeType == XmlNodeType.Text)
            {
                return reader.ReadContentAsString();
            }

            ThrowInvalidDataException();
            return null;
        }

        [DoesNotReturn]
        private static void ThrowEndOfStreamException() => throw new EndOfStreamException();

        [DoesNotReturn]
        private static void ThrowInvalidDataException() => throw new InvalidDataException();
    }
}
